package com.javalab.recorder.audio;

import java.util.ArrayList;
import java.util.List;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;

import org.apache.log4j.Logger;

import com.javalab.recorder.config.ApplicationContext;

public class AudioDevice {

	private List<Mixer.Info> targetMixerInfoList = null;

	private static AudioDevice audioDevice;

	private static Logger logger = Logger.getLogger(AudioDevice.class);

	/**
	 * Get a singleton instance of AudioDevice
	 * @return
	 */
	public static AudioDevice getInstance() {
		synchronized(AudioDevice.class) {
			if (audioDevice == null) {
				audioDevice = new AudioDevice();
			}
			return audioDevice;
		}
	}

	private AudioDevice() {
		Line.Info targetLineInfo = new Line.Info(TargetDataLine.class);
		targetMixerInfoList = new ArrayList<Mixer.Info>();
		for (Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) {
			Mixer mixer = AudioSystem.getMixer(mixerInfo);
			if (mixer.isLineSupported(targetLineInfo)) {
				targetMixerInfoList.add(mixerInfo);
			}
		}
	}

	/**
	 * Get Mixer.Info List which support TargetDataLine
	 * @return
	 */
	public List<Mixer.Info> getTargetMixerInfoList() {
		return targetMixerInfoList;
	}

	/**
	 * Get TargetDataLine by Mixer.Info
	 * @param mixerInfo
	 * @return TargetDataLine
	 */
	public TargetDataLine getTargetDataLineByMixerInfo(Mixer.Info mixerInfo) {
		TargetDataLine targetDataLine = null;
		try {
			Mixer mixer = AudioSystem.getMixer(mixerInfo);
			targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(TargetDataLine.class));
		} catch (LineUnavailableException e) {
			logger.error(e.getMessage(), e);
		}
		return targetDataLine;
	}

	/**
	 * Get Default TargetDataLine which belongs to the Mixer which has been configured in Application Context,
	 * or get the TargetDataLine of the Mixer which is the only one in this machine.
	 * Otherwise null will be returned.
	 * @return
	 */
	public TargetDataLine getDefaultTargetDataLine() {
		if (targetMixerInfoList.size() == 0) {
			return null;
		}
		String defaultTargetMixerName = ApplicationContext.getDefaultCapturingMixerName();
		if (defaultTargetMixerName != null || targetMixerInfoList.size() == 1) {
			for (Mixer.Info mixerInfo : targetMixerInfoList) {
				if (mixerInfo.getName().equals(defaultTargetMixerName)) {
					Mixer mixer = AudioSystem.getMixer(mixerInfo);
					TargetDataLine targetDataLine = null;
					try {
						targetDataLine = (TargetDataLine) mixer.getLine(new Line.Info(TargetDataLine.class));
					} catch (LineUnavailableException e) {
						logger.error(e.getMessage(), e);
					}
					return targetDataLine;
				}
			}
		}
		return null;
	}

	public Clip getDefaultClip() {
		Clip clip = null;
		try {
			clip = AudioSystem.getClip();
		} catch (LineUnavailableException e) {
			logger.error(e.getMessage(), e);
		}
		return clip;
	}

	public SourceDataLine getDefaultSourceDataLine() {
		Line.Info sourceDataLineInfo = new Line.Info(SourceDataLine.class);
		SourceDataLine sourceDataLine = null;
		try {
			sourceDataLine = (SourceDataLine) AudioSystem.getLine(sourceDataLineInfo);
		} catch (LineUnavailableException e) {
			logger.error(e.getMessage(), e);
		}
		return sourceDataLine;
	}

	public AudioFormat getDefaultAudioFormat() {
		AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100.0F, 16, 2, 4, 44100.0F, false);
		return audioFormat;
	}
}
